﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Windows.Threading;
using MFToolkit.Net.XBee;

namespace LaunchPad.Core
{
    public sealed class XBeeIO : DispatcherObject, IDisposable
    {
        private static XBee _xbee;

        private static string _serialPortName;

        private static readonly Dictionary<int, Action<TxStatusType>> PendingCompleteFrameActions =
            new Dictionary<int, Action<TxStatusType>>();

        private static readonly object PendingCompleteFrameActionsLock = new object();

        private XBeeIO()
        {
        }

        public static XBeeIO Instance
        {
            get { return Nested.instance; }
        }

        public string SerialPortName
        {
            get { return _serialPortName; }
            set
            {
                if (_serialPortName != value)
                {
                    _serialPortName = value;
                    TryCreateXBee(value, out _xbee);
                    OnPropertyChanged("SerialPortName");
                }
            }
        }

        public static IEnumerable<string> SerialPortNames
        {
            get { return SerialPort.GetPortNames(); }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (_xbee != null)
                _xbee.Dispose();
        }

        #endregion

        public event EventHandler NodeDiscoveryStarting;
        public event EventHandler NodeDiscovered;
        public event EventHandler NodeDiscoveryComplete;

        public void BeginNodeDiscovery()
        {
            if (_xbee == null)
                return;

            if (!_xbee.IsOpen)
                TryFindXBee();

            if (NodeDiscoveryStarting != null)
                NodeDiscoveryStarting(this, null);

            if(_xbee != null)
                _xbee.ExecuteNonQuery(new NodeDiscoverCommand());
        }

        public void BeginRemotePortSet(XBeeAddress64 address, XBeeAddress16 shortAddress, int port, DIOnMode mode)
        {
            if (_xbee == null)
                return;

            if (!_xbee.IsOpen)
                TryFindXBee();

            var diocmd = new DIOnCommand((uint) port, mode);
            _xbee.ExecuteNonQuery(new RemoteAtRequest(address, shortAddress, true, diocmd));
        }

        public void BeginRemoteTx(XBeeAddress64 address, byte[] data, Action<TxStatusType> onComplete)
        {
            if (_xbee == null)
                return;

            if (!_xbee.IsOpen)
                TryFindXBee();

            var txReq = new TxRequest64(address, data);

            if (onComplete != null)
            {
                lock (PendingCompleteFrameActionsLock)
                {
                    PendingCompleteFrameActions[txReq.FrameID] = onComplete;
                }
            }

            _xbee.ExecuteNonQuery(txReq);
        }

        public void SetNodeId(XBeeAddress64 address, XBeeAddress16 shortAddress, string id)
        {
            if (_xbee == null)
                return;

            if (!_xbee.IsOpen)
                TryFindXBee();

            var nodeIdentifierCommand = new NodeIdentifierCommand(id);
            _xbee.ExecuteNonQuery(new RemoteAtRequest(address, shortAddress, true, nodeIdentifierCommand));

            var applyChangesCommand = new ApplyChangesCommand();
            _xbee.ExecuteNonQuery(new RemoteAtRequest(address, shortAddress, true, applyChangesCommand));
        }

        private bool TryCreateXBee(string portName, out XBee xbee)
        {
            xbee = new XBee(portName);
            try
            {
                xbee.FrameReceived += XbeeFrameReceived;
                xbee.Open();

                if (xbee.Ping())
                {
                    return true;
                }
            }
            catch (IOException) {}
            catch (UnauthorizedAccessException) {}

            xbee.Dispose();
            xbee = null;

            return false;
        }

        public bool TryFindXBee()
        {
            return SerialPortNames.Any(TryFindXBee);
        }

        public bool TryFindXBee(string port)
        {
            XBee xbee;
            if (TryCreateXBee(port, out xbee))
            {
                _serialPortName = xbee.SerialPort.PortName;
                _xbee = xbee;
                return true;
            }
            return false;
        }

        public bool IsAvailable
        {
            get { return _xbee != null; }
        }

        private void XbeeFrameReceived(object sender, FrameReceivedEventArgs e)
        {
            if (e.Response is IAtCommandResponse)
            {
                var response = e.Response as IAtCommandResponse;
                switch (response.Command)
                {
                    case "ND":
                        Debug.WriteLine("Received ND response");
                        ProcessNodeDiscover(response);
                        break;
                }
            }
            else if (e.Response is TxStatusResponse)
            {
                var response = e.Response as TxStatusResponse;
                Action<TxStatusType> action;
                lock (PendingCompleteFrameActionsLock)
                {
                    if (PendingCompleteFrameActions.TryGetValue(response.FrameID, out action))
                    {
                        action.BeginInvoke(response.Status, null, null);
                        PendingCompleteFrameActions.Remove(response.FrameID);
                    }
                }
            }
        }

        private void ProcessNodeDiscover(IAtCommandResponse response)
        {
            if (response.Value != null && response.Value.Length > 0)
            {
                NodeDiscover nd = NodeDiscover.Parse(response);
                Dispatcher.BeginInvoke(new NodeDiscoveredDelegate(OnNodeDiscovered), nd);
            }
            else
            {
                Dispatcher.BeginInvoke(new NodeDiscoveryCompleteDelegate(OnNodeDiscoveryComplete));
            }
        }

        private void OnNodeDiscovered(NodeDiscover nodeDiscover)
        {
            if (NodeDiscovered != null)
                NodeDiscovered(this, new NodeDiscoverEventArgs(nodeDiscover));
        }

        private void OnNodeDiscoveryComplete()
        {
            if (NodeDiscoveryComplete != null)
                NodeDiscoveryComplete(this, null);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #region Nested type: Nested

        private class Nested
        {
            // Explicit static constructor to tell C# compiler
            // not to mark type as beforefieldinit

            internal static readonly XBeeIO instance = new XBeeIO();
        }

        #endregion

        #region Nested type: NodeDiscoveredDelegate

        private delegate void NodeDiscoveredDelegate(NodeDiscover node);

        #endregion

        #region Nested type: NodeDiscoveryCompleteDelegate

        private delegate void NodeDiscoveryCompleteDelegate();

        #endregion

        //void xbee_FrameReceived(object sender, FrameReceivedEventArgs e)
        //{
        //    if (e.Response is IAtCommandResponse)
        //    {
        //        IAtCommandResponse response = e.Response as IAtCommandResponse;
        //        switch (response.Command)
        //        {
        //            case "ND":
        //                if (response.Value != null && response.Value.Length > 0)
        //                {
        //                    NodeDiscover nd = NodeDiscover.Parse(response);
        //                    this.Dispatcher.BeginInvoke(_nodeDiscoveredDelegate, nd);
        //                }
        //                else
        //                {
        //                    this.Dispatcher.BeginInvoke(_commandCompleteDelegate);
        //                }
        //                break;
        //            case "D0":
        //            case "D1":
        //            case "D2":
        //            case "D3":
        //            case "D4":
        //                this.Dispatcher.BeginInvoke(_commandCompleteDelegate);
        //                break;
        //        }
        //    }
        //    else if (e.Response is RxResponse16)
        //    {
        //        RxResponse16 response = e.Response as RxResponse16;
        //    }
        //}
    }

    public class NodeDiscoverEventArgs : EventArgs
    {
        public NodeDiscoverEventArgs(NodeDiscover nodeDiscover)
        {
            NodeDiscover = nodeDiscover;
        }

        public NodeDiscover NodeDiscover { get; set; }
    }
}